#ifndef COMMON_INTERFACE_SHADER_H
#define COMMON_INTERFACE_SHADER_H

#include <glad/glad.h>
#include <glm/glm.hpp>
#include <sstream>
#include <string>

namespace gl
{
	class IShader
	{
	public:
		IShader(const IShader& shader) = delete;

	protected:
		explicit IShader(const std::string & programName_)
		{
			//创建着色器程序
			programHandle = glCreateProgram();
			programName = programName_;
		}

		~IShader()
		{
			//删除着色器程序
			glDeleteProgram(programHandle);
		}

		inline void LoadVertexShaderSource(const char* source) { vertexSource = source; }

		inline void LoadFragmentShaderSource(const char* source) { fragmentSource = source; }

		inline bool CompileShaderAndLink()
		{
			int success;
			char info[2048];
			//创建顶点着色器与片段着色器
			uint32_t vertexShader = glCreateShader(GL_VERTEX_SHADER);
			uint32_t fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
			if(vertexSource.length())
			{
				//顶点着色器源码
				const char* source = vertexSource.c_str();
				//源码绑定
				glShaderSource(vertexShader, 1, &source, nullptr);
				//着色器编译
				glCompileShader(vertexShader);
				//查询编译状态
				glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
				if(!success)
				{
					//获取错误信息
					glGetShaderInfoLog(vertexShader, sizeof(info), nullptr, info);
					std::stringstream ss;
					ss << "Vertex shader of program " << programName << " Compile error:\n" << info;
					errorInfo = ss.str();
					return false;
				}
				//将着色器附加到着色器程序上
				glAttachShader(programHandle, vertexShader);
			}
			if(fragmentSource.length())
			{
				const char* source = fragmentSource.c_str();
				glShaderSource(fragmentShader, 1, &source, nullptr);
				glCompileShader(fragmentShader);
				glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
				if(!success)
				{
					glGetShaderInfoLog(fragmentShader, sizeof(info), nullptr, info);
                    std::stringstream ss;
                    ss << "Vertex shader of program " << programName << " Compile error:\n" << info;
                    errorInfo = ss.str();
					return false;
				}
				glAttachShader(programHandle, fragmentShader);
			}
			//连接着色器程序
			glLinkProgram(programHandle);
			//检查着色器程序连接状态
			glGetProgramiv(programHandle, GL_LINK_STATUS, &success);
			if(!success)
			{
				//获取错误信息
				glGetProgramInfoLog(programHandle, sizeof(info), nullptr, info);
				errorInfo = "Program " + programName + " link error:\n" + info;
				return false;
			}
			//删除没用的着色器
			glDeleteShader(vertexShader);
			glDeleteShader(fragmentShader);
			return true;
		}

		[[nodiscard]] inline const char* GetErrorInfo() const { return errorInfo.c_str(); }

		[[nodiscard]] inline uint32_t GetProgramHandle() const { return programHandle; }

		[[nodiscard]] inline const std::string& GetProgramName() const { return programName; }

	private:
		uint32_t programHandle = 0;
		std::string vertexSource;
		std::string fragmentSource;
		std::string errorInfo;
		std::string programName;

	public:
		inline void UseShader() const
		{
			//激活着色器程序
			glUseProgram(programHandle);
		}

        void setBool(const std::string &name, bool value) const
        {
            glUniform1i(glGetUniformLocation(programHandle, name.c_str()), (int)value);
        }

        void setInt(const std::string &name, int value) const
        {
            glUniform1i(glGetUniformLocation(programHandle, name.c_str()), value);
        }

        void setFloat(const std::string &name, float value) const
        {
            glUniform1f(glGetUniformLocation(programHandle, name.c_str()), value);
        }

        void setVec2(const std::string &name, const glm::vec2 &value) const
        {
            glUniform2fv(glGetUniformLocation(programHandle, name.c_str()), 1, &value[0]);
        }

        void setVec2(const std::string &name, float x, float y) const
        {
            glUniform2f(glGetUniformLocation(programHandle, name.c_str()), x, y);
        }

        void setVec3(const std::string &name, const glm::vec3 &value) const
        {
            glUniform3fv(glGetUniformLocation(programHandle, name.c_str()), 1, &value[0]);
        }

        void setVec3(const std::string &name, float x, float y, float z) const
        {
            glUniform3f(glGetUniformLocation(programHandle, name.c_str()), x, y, z);
        }

        void setVec4(const std::string &name, const glm::vec4 &value) const
        {
            glUniform4fv(glGetUniformLocation(programHandle, name.c_str()), 1, &value[0]);
        }

        void setVec4(const std::string &name, float x, float y, float z, float w) const
        {
            glUniform4f(glGetUniformLocation(programHandle, name.c_str()), x, y, z, w);
        }

        void setMat2(const std::string &name, const glm::mat2 &mat) const
        {
            glUniformMatrix2fv(glGetUniformLocation(programHandle, name.c_str()), 1, GL_FALSE, &mat[0][0]);
        }

        void setMat3(const std::string &name, const glm::mat3 &mat) const
        {
            glUniformMatrix3fv(glGetUniformLocation(programHandle, name.c_str()), 1, GL_FALSE, &mat[0][0]);
        }

        void setMat4(const std::string &name, const glm::mat4 &mat) const
        {
            glUniformMatrix4fv(glGetUniformLocation(programHandle, name.c_str()), 1, GL_FALSE, &mat[0][0]);
        }

        void setMat4Array(const std::string &name, int count, const glm::mat4 *mat) const
        {
            glUniformMatrix4fv(glGetUniformLocation(programHandle, name.c_str()), count, GL_FALSE, &mat[0][0][0]);
        }

		std::string GetShaderLog() const {
			char info[2048];
			glGetProgramInfoLog(programHandle, sizeof(info), nullptr, info);
			std::stringstream ss;
			ss << "Program " << programName << " log:\n" << info;
			return ss.str();
		}
	};
}

#endif //COMMON_INTERFACE_SHADER_H
